package org.sean.framework.aspect;

import cn.hutool.core.thread.ThreadUtil;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.sean.framework.annotation.SyncCollection;
import org.sean.framework.annotation.SyncMethod;
import org.sean.framework.annotation.SyncNormal;
import org.sean.framework.annotation.SyncObject;
import org.sean.framework.code.StatusInfo;
import org.sean.framework.exception.StatusException;
import org.sean.framework.logging.Logger;
import org.sean.framework.redis.RedisService;
import org.sean.framework.util.AspectUtil;
import org.sean.framework.util.DigestUtil;
import org.sean.framework.util.GSONUtil;
import org.sean.framework.util.StringUtil;
import org.sean.framework.web.config.ThrowableHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 同步注解处理器
 *
 * @author xielei
 */
@Aspect
@Order(0)
@SuppressWarnings({"unchecked", "unused"})
public class SynchronizationAspect {

    private final Logger logger = Logger.newInstance(getClass());

    /**
     * 锁续约
     */
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(8);

    /**
     * 锁记录
     */

    private final ThreadLocal<String> keys = new ThreadLocal<>();

    private RedisService redisService;

    public SynchronizationAspect(RedisService redisService) {
        this.redisService = redisService;
    }

    /**
     * 线程开启同步
     */
    private Object openSync(ProceedingJoinPoint point, int index, Function<Object, String> fun, int expire, int timeout,
                            boolean timeoutReturn, boolean timeoutException, boolean ignore) throws Throwable {
        String key = AspectUtil.getMethodName(point);
        Object[] argsArray = point.getArgs();
        if (index >= 0 && argsArray != null && argsArray.length > 0) {
            Object argObj = argsArray[index];
            if (argObj == null) {
                noSyncWarning(point);
            } else {
                key += fun.apply(argObj);
            }
        }
        return wait(point, DigestUtil.md5Hex(key), expire, timeout, timeoutReturn, timeoutException, ignore);
    }

    /**
     * 基本类型参数同步
     *
     * @param point JoinPoint
     * @param sync  SyncMethod
     * @return result
     * @throws Throwable Throwable
     */
    @Around("@annotation(sync)")
    public Object normal(ProceedingJoinPoint point, SyncNormal sync) throws Throwable {
        return openSync(point, sync.index(), Object::toString, sync.expire(), sync.timeout(), sync.timeoutReturn(),
                sync.timeoutException(), sync.ignore());
    }

    /**
     * 复合类型参数同步
     *
     * @param point JoinPoint
     * @param sync  SyncMethod
     * @return result
     * @throws Throwable Throwable
     */
    @Around("@annotation(sync)")
    public Object obj(ProceedingJoinPoint point, SyncObject sync) throws Throwable {
        return openSync(point, sync.index(), arg -> getBeanKey(sync.key(), arg), sync.expire(),
                sync.timeout(), sync.timeoutReturn(), sync.timeoutException(), sync.ignore());
    }

    /**
     * 集合类型参数同步
     *
     * @param point JoinPoint
     * @param sync  SyncMethod
     * @return result
     * @throws Throwable Throwable
     */
    @Around("@annotation(sync)")
    public Object collection(ProceedingJoinPoint point, SyncCollection sync) throws Throwable {
        return openSync(point, sync.index(), arg -> {
            Collection<Object> collection = (Collection<Object>) arg;
            int index = sync.keyIndex();
            if (collection.size() < index || index < 0) {
                noSyncWarning(point);
                return "";
            }
            Object value = null;
            Iterator<Object> iterator = collection.iterator();
            for (int i = 0; i <= index; i++) {
                value = iterator.next();
            }
            String keyValue;
            if (value == null) {
                keyValue = "";
            } else if (value.getClass().isPrimitive() || StringUtil.isEmpty(sync.key())) {
                keyValue = value.toString();
            } else {
                keyValue = getBeanKey(sync.key(), value);
            }
            return keyValue;
        }, sync.expire(), sync.timeout(), sync.timeoutReturn(), sync.timeoutException(), sync.ignore());
    }

    /**
     * 方法同步
     *
     * @param point JoinPoint
     * @param sync  SyncMethod
     * @return result
     * @throws Throwable Throwable
     */
    @Around("@annotation(sync)")
    public Object method(ProceedingJoinPoint point, SyncMethod sync) throws Throwable {
        return openSync(point, -1, arg -> "", sync.expire(), sync.timeout(),
                sync.timeoutReturn(), sync.timeoutException(), sync.ignore());
    }


    private void noSyncWarning(ProceedingJoinPoint point) {
        try {
            Method method = AspectUtil.getMethod(point);
            logger.warn("同步锁注解配置错误: {}.{}", method.getDeclaringClass(), method.getName());
        } catch (Exception e) {
            // Do Nothing
            logger.printStackTrace(e);
        }
    }

    /**
     * 同步等待处理
     *
     * @param key              锁标示
     * @param expire           锁过期时间
     * @param timeout          等待超时时间
     * @param timeoutException 超时后是否抛异常
     * @param ignore           未获取锁是否忽略本次操作
     */
    private Object wait(ProceedingJoinPoint point, String key, int expire, int timeout, boolean timeoutReturn,
                        boolean timeoutException, boolean ignore) throws Throwable {
        if (StringUtil.isEmpty(key)) {
            throw new StatusException(StatusInfo.paramsInvalidError());
        }
        if (key.equals(keys.get())) {
            return point.proceed();
        }
        Object result = null;
        ScheduledFuture<?> future = null;
        Boolean lock = false;
        try {
            long time = redisService.getCurrentTime();
            long timeoutTime = time + timeout;
            BoundValueOperations<String, String> boundValueOps = redisService.getStringTemplate().boundValueOps(key);
            while (true) {
                // 获取锁
                lock = boundValueOps.setIfAbsent("1", expire, TimeUnit.MILLISECONDS);
                if (Boolean.TRUE.equals(lock)) {
                    // 加锁成功// 加锁成功
                    keys.set(key);
                    future = scheduledExecutor.scheduleAtFixedRate(() -> {
                        // 锁续约
                        if (boundValueOps.getExpire() != null) {
                            boundValueOps.expire(expire, TimeUnit.MILLISECONDS);
                        }
                    }, expire / 2, expire / 2, TimeUnit.MILLISECONDS);
                    result = point.proceed();
                    break;
                } else if (ignore) {
                    // 忽略本次执行
                    break;
                } else if (timeoutException || timeoutReturn) {
                    //超时需要抛出异常
                    long currentTime = redisService.getCurrentTime();
                    if (timeoutTime < currentTime) {
                        //超过超时时间,抛出异常
                        if (timeoutReturn) {
                            break;
                        } else {
                            throw new StatusException(StatusInfo.syncTimeoutError());
                        }
                    }
                    ThreadUtil.sleep(5);
                } else {
                    Long expireTime = boundValueOps.getExpire();
                    if (expireTime != null) {
                        //锁未过期,继续等待
                        ThreadUtil.sleep(5);
                    }
                }
            }
        } catch (Throwable e) {
            throw new StatusException(ThrowableHandler.handle(e), e);
        } finally {
            // 取消 锁续约任务
            if (future != null) {
                future.cancel(true);
            }
            // 上锁的线程解锁
            if (Boolean.TRUE.equals(lock)) {
                redisService.delete(key);
            }
            keys.remove();
        }
        return result;
    }

    /**
     * 获取对象的key
     */
    private String getBeanKey(String name, Object value) {
        JsonElement root = JsonParser.parseString(GSONUtil.obj2Json(value));
        if (root == null) {
            return null;
        }

        JsonObject element = root.getAsJsonObject();
        if (element == null) {
            return null;
        }

        StringBuilder result = new StringBuilder();
        String[] keyParts = name.split("\\|");
        for (String key : keyParts) {
            result.append(GSONUtil.getJsonNodeValue(element, key));
        }

        return result.toString();
    }
}
